/*
 * Sandora, a Falling Sand Game for the Pandora
 * Copyright (C) 2011  WaveHack <projects.sandora@wavehack.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <SDL/SDL.h>
#include <math.h>
#include "common.h"
#include "particle.h"

#define PARTICLECANVAS_WIDTH  (SCREEN_WIDTH / PARTICLE_ZOOM)
#define PARTICLECANVAS_HEIGHT (SCREEN_HEIGHT / PARTICLE_ZOOM)

#define SQR(x)   ((x) * (x))
#define ABS(x)   (((x) < 0) ? -(x) : (x))
#define ONEIN(i) ((rand() % (i)) == 0)

// Global variables
particleTypeId_t currentParticleType = PT_SAND;

// Static variables
static particleType_s particleTypes[NUM_PARTICLE_TYPES];
static particleTypeId_t *particleScreen;

// Static prototypes
static inline void renderParticle16(int x, int y, particleTypeId_t pt);
static void doParticleLogic(int x, int y);
static inline particleTypeId_t *getParticleTypeAt(int x, int y);
static inline particleType_s *getParticleAt(int x, int y);

// Global functions

void initParticles(void) {
	// Fill default values
	int i;
	for (i = 0; i < NUM_PARTICLE_TYPES; i++) {
		particleTypes[i].name = "-unused-";
		particleTypes[i].color = 0;
		particleTypes[i].selectable = true;
		//particleTypes[i].spread = 1;
		particleTypes[i].gravity = 0.f;
	}

	particleTypes[PT_NONE].name = "None";

	particleTypes[PT_WALL].name = "Wall";
	particleTypes[PT_WALL].color = SDL_MapRGB(screen->format, 100, 100, 100);

	particleTypes[PT_WATER].name = "Water";
	particleTypes[PT_WATER].color = SDL_MapRGB(screen->format, 32, 32, 255);
	//particleTypes[PT_WATER].spread = 4;
	particleTypes[PT_WATER].gravity = .95f;

	particleTypes[PT_SAND].name = "Sand";
	particleTypes[PT_SAND].color = SDL_MapRGB(screen->format, 238, 204, 128);
	particleTypes[PT_SAND].gravity = .5f;

	particleScreen = (particleTypeId_t*)malloc(sizeof(particleTypeId_t) * (PARTICLECANVAS_WIDTH * PARTICLECANVAS_HEIGHT));
}

void deinitParticles(void) {
	free(particleScreen);
}

void updateParticles(void) {
	int x, y;
	for (y = PARTICLECANVAS_HEIGHT - 1; y--;) {
		if ((y & 0x1) == 1)
			for (x = 1; x < PARTICLECANVAS_WIDTH - 2; x++)
				doParticleLogic(x, y);
		else
			for (x = PARTICLECANVAS_WIDTH - 2; x > 1; x--)
				doParticleLogic(x, y);
	}
}

void drawParticles(void) {
	if (SDL_MUSTLOCK(screen))
		if (SDL_LockSurface(screen) < 0)
			return;

	int x, y;
	for (y = PARTICLECANVAS_HEIGHT; y--;) {
		for (x = PARTICLECANVAS_WIDTH; x--;) {
			if (*getParticleTypeAt(x, y) == PT_NONE)
				continue;

			renderParticle16(x, y, *getParticleTypeAt(x, y));
		}
	}

	if (SDL_MUSTLOCK(screen))
		SDL_UnlockSurface(screen);
}

void addParticlesCircle(int x, int y, int radius) {
	int x2, y2;
	for (x2 = ((x - radius - 1) < 0) ? 0 : (x - radius - 1); x2 <= (x + radius) && (x2 < PARTICLECANVAS_WIDTH); x2++)
		for (y2 = ((y - radius - 1) < 0) ? 0 : (y - radius - 1); y2 <= (y + radius) && (y2 < PARTICLECANVAS_HEIGHT); y2++)
			if ((SQR(x2 - x) + SQR(y2 - y)) <= SQR(radius))
				*getParticleTypeAt(x2, y2) = currentParticleType;
}

void clearParticleScreen(void) {
	int x, y;
	for (y = 0; y < PARTICLECANVAS_HEIGHT; y++)
		for (x = 0; x < PARTICLECANVAS_WIDTH; x++)
			*getParticleTypeAt(x, y) = PT_NONE;
}


// Static functions

static inline void renderParticle16(int x, int y, particleTypeId_t pt) {
#if (PARTICLE_ZOOM == 1)
	*((Uint16*)screen->pixels + (((y * screen->pitch) / 2) + x)) = particleTypes[pt].color;
#else
	SDL_Rect dst;
	dst.x = x * PARTICLE_ZOOM;
	dst.y = y * PARTICLE_ZOOM;
	dst.w = PARTICLE_ZOOM;
	dst.h = PARTICLE_ZOOM;

	SDL_FillRect(screen, &dst, particleTypes[pt].color);
#endif
}

static void doParticleLogic(int x, int y) {
	// Reference to selected particle type we're updating (read only)
	particleType_s currentParticle = *getParticleAt(x, y);
	particleTypeId_t currentParticleType = *getParticleTypeAt(x, y);

	// Handle particle gravity. Particle gravity higher than 0.f are falling
	// down, lower than 0.f are floating up and exactly 0.f means it's a still.
	// Gravity also affects spread rate (todo: different variable)

	// Falling
	if (currentParticle.gravity > 0.f) {

		// If we're allowed to do gravity this turn
		if (((float)rand() / (float)RAND_MAX) <= currentParticle.gravity) {
		//if (ONEIN((int)(1.0f / currentParticle.gravity))) {
			// Check free space down first
			if (*getParticleTypeAt(x, y + 1) == PT_NONE) {
				*getParticleTypeAt(x, y + 1) = currentParticleType;
				*getParticleTypeAt(x, y) = PT_NONE;
				return;
			}

			// If down is taken, check downleft and downright in a random order
			if (ONEIN(2)) {
				// Check left..
				if (*getParticleTypeAt(x - 1, y + 1) == PT_NONE) {
					*getParticleTypeAt(x - 1, y + 1) = currentParticleType;
					*getParticleTypeAt(x, y) = PT_NONE;
					return;

				// before right
				} else if (*getParticleTypeAt(x + 1, y + 1) == PT_NONE) {
					*getParticleTypeAt(x + 1, y + 1) = currentParticleType;
					*getParticleTypeAt(x, y) = PT_NONE;
					return;
				}

			// Other direction
			} else {
				// Check right..
				if (*getParticleTypeAt(x + 1, y + 1) == PT_NONE) {
					*getParticleTypeAt(x + 1, y + 1) = currentParticleType;
					*getParticleTypeAt(x, y) = PT_NONE;
					return;

				// before left
				} else if (*getParticleTypeAt(x - 1, y + 1) == PT_NONE) {
					*getParticleTypeAt(x - 1, y + 1) = currentParticleType;
					*getParticleTypeAt(x, y) = PT_NONE;
					return;
				}
			}

			// If down, downleft and downright are taken, check left and right
			// in a random order (spread)
			if (ONEIN(2)) {
				// Check left..
				if (*getParticleTypeAt(x - 1, y) == PT_NONE) {
					*getParticleTypeAt(x - 1, y) = currentParticleType;
					*getParticleTypeAt(x, y) = PT_NONE;
					return;

				// before right
				} else if (*getParticleTypeAt(x + 1, y) == PT_NONE) {
					*getParticleTypeAt(x + 1, y) = currentParticleType;
					*getParticleTypeAt(x, y) = PT_NONE;
					return;
				}

			// Other direction
			} else {
				// Check right..
				if (*getParticleTypeAt(x + 1, y) == PT_NONE) {
					*getParticleTypeAt(x + 1, y) = currentParticleType;
					*getParticleTypeAt(x, y) = PT_NONE;
					return;

				// before left
				} else if (*getParticleTypeAt(x - 1, y) == PT_NONE) {
					*getParticleTypeAt(x - 1, y) = currentParticleType;
					*getParticleTypeAt(x, y) = PT_NONE;
					return;
				}
			}
		}

	// Floating
	} else if (currentParticle.gravity < 0.f) {

	// Still
	} else {
	}

	// Particle-specific logic
	switch (currentParticleType) {
	default:
		break;
	}
}

//static bool doParticleGravity(int x, int y) {
//	return false;
//}


static inline particleTypeId_t *getParticleTypeAt(int x, int y) {
	return &particleScreen[(x + (y * PARTICLECANVAS_WIDTH)) % (PARTICLECANVAS_WIDTH * PARTICLECANVAS_HEIGHT)];
}

static inline particleType_s *getParticleAt(int x, int y) {
	return &particleTypes[*getParticleTypeAt(x, y)];
}
